package fr.uvsq.isty.bdh.hchess.uci;

import fr.uvsq.isty.bdh.hchess.interfacing.*;
import ictk.boardgame.chess.ChessBoard;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;


/**
 *
 * @author Clement Bellot <clementbellot@gmail.com>
 */
public class UciChessPlayer implements IDisposableChessPlayer {
    private boolean  playsBlack;
    private Process engineProcess;
    /**
     * The program output. What it sends.
     */
    private InputStreamReader in;
    /**
     * The program input. What you sent to it.
     */
    private OutputStream out;
    /**
     * Not used. Maybe sometime, when the UCI will surrender.
     */
    private GameEventRefereeNotifier refereeNotifier;
    /**
     * The string has this format : " a2-a3 a7-a6" and is the list of previously
     * done moves, uci-formated.
     */
    private String prevMoves;
    private boolean isClosed;
    private static final int initTimeout = 3000;
    private static final int yieldMoveTimeout = 500;
    private static final int playTimeLimit = 2000;
    private static final int actualPlayTimeLimit = 2000;
    private boolean takeAccountThisMove;
    /**
     * 
     * @param executable The path to the executable file.
     * @param playsBlack Don't make a mistake on this one, it causes the engine
     * to search endlessly. Quite hard to debug.
     * @throws IOException If the process coudn't be launched. The constructor
     * does not check wether the program has an actual uci interface in the
     * constructor, but in the setup method.
     */
    public UciChessPlayer(String executable,boolean playsBlack)
            throws IOException{
        assert executable!=null;
        engineProcess = Runtime.getRuntime().exec(executable);
        in = new InputStreamReader(engineProcess.getInputStream());
        out = engineProcess.getOutputStream();
        this.isClosed = false;
        this.playsBlack = playsBlack;
        prevMoves = "";
    }
    /**
     * Checks wether the program is actually an uci, and initializes it. Shall
     * be called before any call to sendCommandToUci(String).
     * @throws IOException If the program did not answered correctly before
     * the timeout.
     */
    private void initAndcheckUci() throws IOException{
        sendCommandToUci("uci");
        if(UciCommand.expectLastWithTimeout(in,"uciok",initTimeout)==null){
            throw new IOException("The program does not complies to the UCI");
        }
    }
    
    
    
    /**
     * Sends the given command to the uci.
     * @param command A non '\n' terminated string.
     */
    public void sendCommandToUci(String command) throws IOException{
        out.write((command+"\n").getBytes());
        out.flush();
        System.out.println("Command sent :" + command);
    }
    
    /*
     * Interface IChessPlayer implementation.
     */
    public boolean sendsChat() {
        return false;
    }

    public boolean recivesChat() {
        return false;
    }

    public void notifyChatRecived(String message) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public String getChat() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean handlesNonStandardStarts() {
        return false;
    }
    /**
     * 
     * @param lastMove
     * @return
     * @throws FailedToReachPlayerException If the program could not be sent.
     */
    public int[] getNextMove(int[] lastMove)
            throws FailedToReachPlayerException {
        if(isClosed){
            throw new FailedToReachPlayerException("The UCI was colsed.");
        }
        String toAsk;
        if(lastMove==null){
            toAsk = "position startpos";
        }else{
            if (takeAccountThisMove) {
                prevMoves += " " + Helpers.uciMoveFromIntArray(lastMove);
            } else {
                takeAccountThisMove = true;
            }
            toAsk = "position startpos moves"+prevMoves;
        }
        UciCommand ret;
        try {
            sendCommandToUci(toAsk);
            if(playsBlack){
                sendCommandToUci("go btime "+String.valueOf(playTimeLimit));
            }else{
                sendCommandToUci("go wtime "+String.valueOf(playTimeLimit));
            }
            try {
                Thread.sleep(actualPlayTimeLimit);
            } catch (InterruptedException ex) {
                assert false;
            }
            sendCommandToUci("stop");
            ret = UciCommand.expectLastWithTimeout(
                    in,
                    "bestmove",
                    yieldMoveTimeout);
        } catch (IOException ex) {
            //ex.printStackTrace();
            throw new FailedToReachPlayerException(
                    "Problem comunicating with the UCI engine.");
        }
        try{
            prevMoves += " " + ret.getParameter(0);
            return Helpers.intArrayFromUciMove(ret.getParameter(0));
        }catch(IndexOutOfBoundsException ex){
            throw new FailedToReachPlayerException(
                    "Badly formated UCI engine response.");
        }
    }

    public void notifyFinalMove(int[] lastMove) {
        // I don't care.
    }

    public int[] getNextMove(int[] lastMove, int timeLimit) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void notifyGameEvent(GameEvent event) {
        if(playsBlack){
            if(event==GameEvent.WHITE_ASKED_DRAW){
                
            }
        }
    }
    /*
     * End of interface IChessPlayer implementation.
     */

    /**
     * Closes the program.
     */
    public void close() {
        try{
            sendCommandToUci("exit");
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            // Don't care. Go Go Go.
        }catch(IOException e){
            // If we can't do it easy, we are going to do it hard.
        }
        engineProcess.destroy();
        isClosed = true;
    }
    
    /**
     * The class handles the game resuming.
     * @return true
     */
    public boolean handlesResume() {
        return true;
    }

    /**
     * Tries to initialize the uci interface of the program.
     * @param startPos Not supported.
     * @throws FailedToReachPlayerException If the program fails to use the uci
     * interface or times out.
     */
    public void setUp(
            GameEventRefereeNotifier notifier,
            ChessBoard startPos,
            int[][] stateToRestore)
                throws FailedToReachPlayerException {
        try {
            initAndcheckUci();
            refereeNotifier = notifier;
            prevMoves = "";
           sendCommandToUci("isready");
           UciCommand.expectLastWithTimeout(in, "readyok", yieldMoveTimeout);
           sendCommandToUci("ucinewgame");
           //sendCommandToUci("debug off");
        } catch (IOException ex) {
            throw new FailedToReachPlayerException(
                    "The UCI did not answered as expected");
        }
        if(stateToRestore.length==0){
            takeAccountThisMove = true;
        }else{
            takeAccountThisMove = playsBlack;
            for(int[] move : stateToRestore){
                prevMoves += " " + Helpers.uciMoveFromIntArray(move);
                takeAccountThisMove = !takeAccountThisMove;
            }
        }
    }
}
